package org.example;

import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Callback;
import javafx.util.Duration;
import jfxtras.styles.jmetro.JMetro;
import jfxtras.styles.jmetro.Style;
import org.apache.commons.io.FileUtils;
import org.example.appstart.StartAppInfo;
import org.example.appstart.StartAppManagerController;
import org.example.constant.Resource;
import org.example.exception.ExceptionReporter;
import org.example.log.Log;
import org.example.task.Task;
import org.example.trace.TraceElementManagerController;
import org.example.utlis.CmdUtils;
import org.example.utlis.FXUtils;
import org.example.utlis.TimeUtils;

import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;

public class MainController {
    private static CmdUtils cmd;
    private Stage mStage;
    private ArrayList<Device> devices = new ArrayList<>();
    private ArrayList<String> deviceIds = new ArrayList<>();
    private HashMap<String, Pane> funcMap = new LinkedHashMap<>();
    private String traceSelectedTags = "";
    private ObservableList<StartAppInfo> startAppInfoList;
    public MainController() {
        CmdUtils.init(new CmdUtils.OnExeCmdListener() {
            @Override
            public void onSucceed(String msg) {
                append(msg);
            }
            @Override
            public void onFail(String msg) {
                appendError(msg);
            }
        });
        cmd = CmdUtils.getInstance();
    }

    @FXML
    private ListView<String> lvMenu;
    @FXML
    private AnchorPane layoutJit, layoutYlog, layoutScreenRecord, layoutAppStart, layoutTrace;
    @FXML
    private TextField tJitExeTimes, tStartTimes, tInputApp, tCmd, tTraceDuration, tTraceTimes;
    @FXML
    private TextArea tConsole;
    @FXML
    private ToggleGroup groupJitDevice, groupJitPkg, groupStartMode, groupStartHotBack, groupStartRemove;
    @FXML
    private ChoiceBox<String> cbDevices;
    @FXML
    private ChoiceBox<String> cbCompileMode;
    private final String[] compileModes = new String[]{"profile", "speed"};
    //用于存放控制台输出消息的队列
    private final ArrayList<String> msgList = new ArrayList<>();
    //用于控制台输出冷却
    private long lastTime = 0;
    //控制台冷却时间
    private final long COLD_TIME = 500;
    private final Runnable msgRunnable = new Runnable() {
        @Override
        public void run() {
            while (true) {
                if (msgList.isEmpty()) {
                    try {
                        Thread.sleep(COLD_TIME);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    continue;
                }
                //消息队列中有消息，判断冷却
                long currentTime = System.currentTimeMillis();
                if (currentTime - lastTime < COLD_TIME) {
                    continue;
                }
                //冷却ok，输出一条消息
                synchronized (msgList) {
                    String finalMsg = msgList.get(0);
                    msgList.remove(0);
                    Platform.runLater(() -> tConsole.appendText(finalMsg));
                    Log.saveLog(finalMsg);
                    System.out.println("输出：" + finalMsg);
                }
            }
        }
    };
    @FXML
    public void initialize() {
        new Thread(msgRunnable).start();
        updateDevicesAndIds();
        initFunctionMenu();
        for (int i = 0; i < compileModes.length; i++) {
            cbCompileMode.getItems().add(compileModes[i]);
        }
        cbCompileMode.setValue(compileModes[0]);
    }

    private void initFunctionMenu() {
        funcMap.put(Resource.MENU_YLOG, layoutYlog);
        funcMap.put(Resource.MENU_JIT, layoutJit);
        funcMap.put(Resource.MENU_APP_START, layoutAppStart);
        funcMap.put(Resource.MENU_TRACE, layoutTrace);
        funcMap.put(Resource.MENU_SCREEN_RECORD, layoutScreenRecord);
        ObservableList<String> list = FXCollections.observableArrayList();
        funcMap.forEach((k,v) -> {
            list.add(k);
            //先隐藏全部
            v.setVisible(false);
        });
        lvMenu.setItems(list);

        lvMenu.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
            if (!(oldValue==null || oldValue.isEmpty())) funcMap.get(oldValue).setVisible(false);
            funcMap.get(newValue).setVisible(true);
        });
        lvMenu.getSelectionModel().select(0);
    }

    private void append(String content) {
        String finalMsg = TimeUtils.getCurrentTime() + ": " + content + "\n";
        synchronized(msgList) {
            msgList.add(finalMsg);
            System.out.println("入队：" + finalMsg);
        }
    }

    private void appendError(String content) {
        Platform.runLater(() -> tConsole.appendText(TimeUtils.getCurrentTime() + ": Error! : " + content + "\n"));
    }

    @FXML
    void onSettingsClicked(MouseEvent event) throws IOException {
        Stage stage = new Stage();
        stage.setResizable(false);
        stage.initStyle(StageStyle.TRANSPARENT);
        FXMLLoader fxmlLoader = new FXMLLoader();
        fxmlLoader.setLocation(getClass().getResource("settings.fxml"));
        Parent load = fxmlLoader.load();
        Scene scene = new Scene(load);
        SettingsController controller = fxmlLoader.getController();
        controller.setStage(stage);

        App.useJMetro(scene);

        stage.setScene(scene);
        stage.show();
    }

    @FXML
    void onClear(ActionEvent event){
        tConsole.clear();
    }

    @FXML
    void onCopy(ActionEvent event) {
        Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
        StringSelection text = new StringSelection(tCmd.getText());
        clip.setContents(text, null);
        tCmd.clear();
    }

    @FXML
    void onFreshDevices(MouseEvent event) {
        updateDevicesAndIds();
    }

    @FXML
    void onGetPkg(ActionEvent event) {
        tCmd.setText(Task.build(cbDevices.getValue(), Task.Type.PACKAGE_NAME).run());
    }

    @FXML
    void onGetApk(ActionEvent event) {
        String pathApk = App.mConfig.get("path_apk");
        if (pathApk == null || pathApk.isEmpty()) {
            FXUtils.initSimpleDialog("提示", "请先在设置中配置apk保存路径！");
            return;
        }
        Task.build(cbDevices.getValue(), Task.Type.APK_PULL, pathApk).run();
        openFolder(pathApk);
    }

    public void setStage(Stage stage) {
        mStage = stage;
    }

    @FXML
    void onExeJit(ActionEvent event) {
        //次数获取
        String sTimes = tJitExeTimes.getText();
        int times = formatNumber(sTimes, 1, 10, 10);
        tJitExeTimes.setText(String.valueOf(times));

        //设备获取
        boolean isAllDevices = getIsAllDevices();
        //应用获取
        boolean isInputApp = false;
        if ("input".equals(groupJitPkg.getSelectedToggle().getUserData())) {
            isInputApp = true;
        }
        int mode = 0;
        //编译过滤器类型
        String value = cbCompileMode.getValue();
        for (int i = 0; i < compileModes.length; i++) {
            if (compileModes[i].equals(value)) {
                mode = i+1; // mode1: profile  mode2: speed
            }
        }

        //调用执行
        if (isAllDevices) {
            for (String deviceId : deviceIds) {
                Task.build(deviceId, Task.Type.JIT, isInputApp ? tInputApp.getText() : "", times, mode).run();
            }
        } else {
            Task.build(cbDevices.getValue(), Task.Type.JIT, isInputApp ? tInputApp.getText() : "", times, mode).run();
        }
    }

    @FXML
    void onStartYlog(MouseEvent event) {
        boolean isAllDevices = getIsAllDevices();
        if (isAllDevices) {
            for (String deviceId : deviceIds) {
                Task.build(deviceId, Task.Type.YLOG_PAGE).run();
            }
        } else {
            Task.build(cbDevices.getValue(), Task.Type.YLOG_PAGE).run();
        }
    }

    @FXML
    void onPullYlog(MouseEvent event) {
        String pathYlog = App.mConfig.get("path_ylog");
        if (pathYlog == null || pathYlog.isEmpty()) {
            FXUtils.initSimpleDialog("提示", "请先在设置中配置ylog保存路径！");
            return;
        }
        boolean isAllDevices = getIsAllDevices();
        if (isAllDevices) {
            for (String deviceId : deviceIds) {
                Task.build(deviceId, Task.Type.YLOG_PULL, pathYlog).run();
            }
        } else {
            Task.build(cbDevices.getValue(), Task.Type.YLOG_PULL, pathYlog).run();
        }
        openFolder(pathYlog);
    }

    @FXML
    void onScreenCap() {
        String pathVideo = App.mConfig.get("path_video");
        if (pathVideo == null || pathVideo.isEmpty()) {
            FXUtils.initSimpleDialog("提示", "请先在设置中配置图片/视频保存路径！");
            return;
        }

        Task.build(cbDevices.getValue(), Task.Type.SCREEN_CAP_AND_PULL, pathVideo).run();
        openFolder(pathVideo);
    }

    @FXML
    void onScreenRecord() {
        FXUtils.initSimpleDialog("提示", "此功能暂时未实现");
//        String pathVideo = App.mConfig.get("path_video");
//        Task.build(cbDevices.getValue(), Task.Type.SCREEN_RECORD_AND_PULL, pathVideo).run();
    }

    @FXML
    void onExeTrace() {
        String savePath = App.mConfig.get("path_trace");
        if (savePath == null || savePath.isEmpty()) {
            FXUtils.initSimpleDialog("提示", "请先在设置中配置trace保存路径！");
            return;
        }
        if (traceSelectedTags == null || traceSelectedTags.isEmpty()) {
            FXUtils.initSimpleDialog("提示", "tag组为空，请先选择参数！");
            return;
        }

        //每次执行多久
        String duration = tTraceDuration.getText();
        int durationInt = formatNumber(duration, 5, 60, 10);
        tTraceDuration.setText(String.valueOf(durationInt));
        //执行次数
        String times = tTraceTimes.getText();
        int timesInt = formatNumber(times, 1, 20, 1);
        tTraceTimes.setText(String.valueOf(timesInt));

        boolean isAllDevices = getIsAllDevices();
        if (isAllDevices) {
            for (String deviceId : deviceIds) {
                Task.build(deviceId, Task.Type.TRACE_PERFETTO, durationInt, savePath, timesInt, traceSelectedTags).run();
            }
        } else {
            Task.build(cbDevices.getValue(), Task.Type.TRACE_PERFETTO, durationInt, savePath, timesInt, traceSelectedTags).run();
        }
        openFolder(savePath);
    }

    @FXML
    void onManageStartAppInfo() throws IOException {
        Stage stage = new Stage();
        stage.setResizable(false);
        stage.initStyle(StageStyle.TRANSPARENT);
        FXMLLoader fxmlLoader = new FXMLLoader();
        fxmlLoader.setLocation(getClass().getResource("appstart/start_app_manager.fxml"));
        Parent load = fxmlLoader.load();
        Scene scene = new Scene(load);
        StartAppManagerController controller = fxmlLoader.getController();
        controller.setStage(stage);
        controller.setDevice(cbDevices.getValue());
        controller.setChooseOverListener(selectedItems -> {
            startAppInfoList = selectedItems;
        });

        App.useJMetro(scene);

        stage.setScene(scene);
        stage.show();
    }

    @FXML
    void onExeStartApp() {
        //获取待测试应用
        if (startAppInfoList == null || startAppInfoList.size() == 0) {
            FXUtils.initSimpleDialog("提示", "请先选择待测试应用！");
            return;
        }

        //获取设备
        boolean isAllDevices = getIsAllDevices();
        //获取测试项：冷/热启动
        boolean isColdStart = "cold".equals(groupStartMode.getSelectedToggle().getUserData());
        //获取热启动返回方式
        boolean isBack = "back".equals(groupStartHotBack.getSelectedToggle().getUserData());
        //获取是否去除最高最低
        boolean isRemove = "yes".equals(groupStartRemove.getSelectedToggle().getUserData());
        //获取测试次数,如果isRemove,则需要多测两次
        int times = formatNumber(tStartTimes.getText(), 5, 10, 5);
        tStartTimes.setText(String.valueOf(times));
        times = isRemove ? times+2 : times;

        //发送任务
        if (isAllDevices) {
            for (String deviceId : deviceIds) {
                Task.build(deviceId, Task.Type.APP_START, startAppInfoList, isColdStart, isBack, isRemove, times).run();
            }
        } else {
            Task.build(cbDevices.getValue(), Task.Type.APP_START, startAppInfoList, isColdStart, isBack, isRemove, times).run();
        }
    }

    private void updateDevicesAndIds() {
        devices.clear();
        deviceIds.clear();
        cmd.getDevicesAsync((devices) -> {
            try {
                for (Device device : devices) {
                    deviceIds.add(device.getDeviceId());
                    this.devices.add(device);
                }
                //UI更新
                Platform.runLater(() -> {
                    cbDevices.setItems(FXCollections.observableArrayList(deviceIds));
                    cbDevices.setValue(deviceIds.get(0));
                });
            } catch (Exception e) {
                ExceptionReporter.saveLog(e, true);
            }
        });
    }

    private void openFolder(String path) {
        try {
            Desktop.getDesktop().open(new File(path));
        } catch (IOException | IllegalArgumentException e) {
            FXUtils.initSimpleDialog("ERROR!", "reason: " + e.getMessage());
        }
    }

    private boolean getIsAllDevices() {
        boolean isAllDevices = true;
        if ("current".equals(groupJitDevice.getSelectedToggle().getUserData())) {
            isAllDevices = false;
        }
        return isAllDevices;
    }

    /**
     * 字符串数字提取，可用于文本输入的格式化
     * @param str 字符串
     * @param min
     * @param max
     * @param defaultNum 缺省值
     * @return
     */
    private int formatNumber(String str, int min, int max, int defaultNum) {
        int number = defaultNum;
        if (str != null && !str.isEmpty()) {
            try {
                number = Integer.valueOf(str);
                number = Math.max(Math.min(number, max),min);
            } catch (NumberFormatException e) {
            }
        }
        return number;
    }

    @FXML
    void test1() throws IOException {
        cmd.getPkgNameCurrentFocusAndLauncherActivity(cbDevices.getValue());
    }

    @FXML
    void test2() {
//        String pathPkgInfo = App.mConfig.get("path_pkg_info");
//        if (pathPkgInfo == null || pathPkgInfo.isEmpty()) {
//            FXUtils.initSimpleDialog("提示", "请先在设置中配置pkginfo保存路径！");
//            return;
//        }
//        boolean isAllDevices = getIsAllDevices();
//        if (isAllDevices) {
//            for (String deviceId : deviceIds) {
//                Task.build(deviceId, Task.Type.DUMP_PACKAGE, pathPkgInfo).run();
//            }
//        } else {
//            Task.build(cbDevices.getValue(), Task.Type.DUMP_PACKAGE, pathPkgInfo).run();
//        }
    }

    @FXML
    public void onElementSelect(ActionEvent actionEvent) throws IOException {
        Stage stage = new Stage();
        stage.setResizable(false);
        stage.initStyle(StageStyle.TRANSPARENT);
        FXMLLoader fxmlLoader = new FXMLLoader();
        fxmlLoader.setLocation(getClass().getResource("trace/trace_element_manager.fxml"));
        Parent load = fxmlLoader.load();
        Scene scene = new Scene(load);
        TraceElementManagerController controller = fxmlLoader.getController();
        controller.setStage(stage);
        controller.setChooseOverListener((String selectedTags) -> {
            //选中后反馈回的tag组
            traceSelectedTags = selectedTags;
        });

        App.useJMetro(scene);

        stage.setScene(scene);
        stage.show();
    }
}
